﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Configuration;
using System.Web.Services;
using System.Data.SqlClient;
using DCS.Domain;
using DCS.Domain.Entities;
using Dcs.Infrastructure;
using DCS.Service.Interfaces;
using DCS.SqlProvider;

namespace DCS.Service
{

    /// <summary>
    /// Summary description for Service1
    /// </summary>
    [WebService(Namespace = "http://dcs.org/",
                Description = "Менеджер системы DCS")]
    [WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]
    [ToolboxItem(false)]
    public class DcsService : WebService,
        IAdminClientMethods, ICalcClientMethods, ICentralService
    {
        private static readonly object PortionsSyncRoot = new object();
        private static readonly object ResultsSyncRoot = new object();

        private static readonly Dictionary<Guid, AdminManager> Admins = new Dictionary<Guid, AdminManager>();
        readonly SqlDataRepository sqlRepository;
        private readonly Repository repository;
        private readonly Settings settings = Settings.Instance;
        /// <summary>
        /// Конструктор веб-сервиса.
        /// </summary>
        public DcsService()
        {
            // Создаём конструктор строк подключения к БД
            var stringBuilder = new SqlConnectionStringBuilder
            {
                DataSource = ConfigurationManager.AppSettings["DataSource"],
                UserID = ConfigurationManager.AppSettings["UserID"],
                Password = ConfigurationManager.AppSettings["Password"],
                InitialCatalog = ConfigurationManager.AppSettings["InitialCatalog"],
                IntegratedSecurity = false,
                AsynchronousProcessing = true
            };

            // Создаём провайдер для работы с базой данных
            sqlRepository = new SqlDataRepository(stringBuilder.ConnectionString);

            repository = new Repository(stringBuilder.ConnectionString);
        }

        [WebMethod]
        public Guid StartAdminSession()
        {
            var guid = Guid.NewGuid();
            Admins.Add(guid, new AdminManager(guid));
            return guid;
        }

        [WebMethod]
        public void StoptAdminSession(Guid sessionId)
        {
            Admins.Remove(sessionId);
        }

        [WebMethod]
        public string AddTask(TaskPack task)
        {
            // Вызываем метод для добавления задачи на сервер.
            sqlRepository.AddTask(task);

            OnTaskUpdated();

            return "Ваше задание добавлено для расчёта.\nИмя задачи: " + task.TaskName +
                   "\nЧисло порций разбиения: " + task.PortionsNumber;
        }

        [WebMethod]
        public TaskResult GetResult(Guid taskId)
        {
            if (sqlRepository.SqlGetTaskProgress(taskId) == 100)
            {
                return sqlRepository.GetTaskResult(taskId);
            }

            return new TaskResult();
        }

        [WebMethod]
        public TaskInfo[] GetAllTaskStates(Guid adminId, bool waitForUpdate)
        {
            if (waitForUpdate)
            {
                Admins[adminId].GetActiveClientsCompleted.WaitOne();
            }

            return sqlRepository.SqlGetAllTasksInfo();
        }

        [WebMethod]
        public TaskInfo[] GetAllTaskInfos(Guid adminId)
        {
            return sqlRepository.SqlGetAllTasksInfo();
        }

        [WebMethod]
        public ServiceWorkStatistics GetStatistics()
        {
            return new ServiceWorkStatistics();
        }

        [WebMethod]
        public ServiceState GetCurrentState()
        {
            return sqlRepository.SqlGetCurrentServiceState();
        }

        [WebMethod(Description = "Получение статистики работы сервиса")]
        public HardwareDescription GetSummaryCharacteristics()
        {
            return sqlRepository.GetSummaryHardwareCharacteristics();
        }


        [WebMethod(Description = "Получение числа активных клиентов")]
        public int GetActiveClientsNumber(DateTime time)
        {
            return sqlRepository.SqlGetActiveClientsNumber(time);
        }

        [WebMethod]
        public int RestartService()
        {
            return sqlRepository.SqlClearAll();
        }

        ///<summary>
        /// Возвращает адрес центрального сервера системы DCS.
        ///</summary>
        ///<returns>Адрес сервера.</returns>
        [WebMethod]
        public string GetCentralServerAddress()
        {
            return settings.CentralServer;
        }

        ///<summary>
        /// Устанавливает адрес центрального сервера системы DCS.
        ///</summary>
        ///<param name="address">Адрес сервера.</param>
        [WebMethod]
        public void SetCentralServerAddress(string address)
        {
            settings.CentralServer = address;
            settings.Save();
        }

        [WebMethod]
        public Dependency[] GetTastDependencies(Guid taskGuid)
        {
            return sqlRepository.SqlGetTaskDependencies(taskGuid);
        }

        [WebMethod]
        public Guid RegisterClient(ComputationalClient client)
        {
            // Если в БД уже имеется клиент с таким ID ...
            if (repository.GetClients(client.Id).Count >= 1)
            {
                repository.Update(client);

                // Возвращаем клиенту ID с которым он был раньше
                return client.Id;
            }
            // Иначе, если в БД отсутствует запись с таким Id
            // Генерируем новый уникальный идентификатор
            repository.Save(client);

            // Возвращаем клиенту его новый мдентификатор););
            return client.Id;
        }

        [WebMethod]
        public TaskPack GetTaskPortion(Guid client)
        {
            lock (PortionsSyncRoot)
            {
                var task = sqlRepository.GetNextTaskPortion();
                
                if (task != null)
                {
                    sqlRepository.SqlMarkTask(task.Guid, TaskState.Active);
                }
                else
                {
                    if (!string.IsNullOrEmpty(settings.CentralServer))
                    {
                        task = GetRetransferredTask();
                    }
                }

                return task;
            }
        }

        private TaskPack GetRetransferredTask()
        {
            try
            {
                var retransferredTask = sqlRepository.GetRetransferredTask();

                // Если нет сохранённых ретранслированных заданий ..
                if (retransferredTask == null)
                {
                    var dcsService =
                        new DCS_Service.DcsService(string.Format("http://{0}/DcsService.asmx",
                                                                 settings.CentralServer));

                    // .. получаем их у центрального DCS сервера
                    var taskPack = dcsService.GetMultiPortionTaskPack();

                    if (taskPack != null)
                    {
                        sqlRepository.AddMultiPortionTaskPack(taskPack);

                        retransferredTask = sqlRepository.GetRetransferredTask();
                    }
                }

                return retransferredTask;
            }
            catch (Exception)
            {
                return null;
            }
        }

        [WebMethod]
        public void SetPortionResult(TaskPortionResult result, Guid client)
        {
            if (result.IsRetransferred)
            {
                if (!string.IsNullOrEmpty(settings.CentralServer))
                {
                    var dcsService =
                        new DCS_Service.DcsService(string.Format("http://{0}/DcsService.asmx",
                                                                      settings.CentralServer));
                    dcsService.RegisterClient(new DCS_Service.ComputationalClient {Id = client});
                    dcsService.SetPortionResult(DcsConverter.ToTaskPortionResult(result),client);
                    sqlRepository.MarkRetransferredPortionCounted(result.TaskGuid, result.PortionNumber, client);
                }
            }
            else
            {
                lock (ResultsSyncRoot)
                {
                    // Запоминаем клиента, выполнившего расчёт порции
                    sqlRepository.MarkPortionCounted(result.TaskGuid, result.PortionNumber, client);

                    // Запоминаем какое задание было посчитанно данным клиентом
                    sqlRepository.SqlSetLastCountedTask(client, result.TaskGuid);

                    // Сохраняем результаты расчёта
                    sqlRepository.SetPartialResult(result);

                    // Если это последняя порция - задание помечаем как готовое
                    if (sqlRepository.SqlGetTaskProgress(result.TaskGuid) == 100)
                    {
                        sqlRepository.SqlMarkTask(result.TaskGuid, TaskState.Completed);
                    }

                    OnTaskUpdated();
                }
            }
        }

        private static void OnTaskUpdated()
        {
            foreach (var adminId in Admins.Keys)
            {
                Admins[adminId].GetActiveClientsCompleted.Set();
            }
        }

        ///<summary>
        /// Возвращает многопорционную задачу для рассчёта.
        /// Метод используется промежуточными серверами для масштабирования вычислений.
        ///</summary>
        ///<returns>Задача, состоящая из нескольких порций.</returns>
        [WebMethod]
        public MultiPortionTaskPack GetMultiPortionTaskPack()
        {
            lock (PortionsSyncRoot)
            {
                return sqlRepository.GetMultiTaskPortion();
            }
        }

        ///<summary>
        /// Устанавливает результат рассчёта многопорционной задачи.
        ///</summary>
        ///<param name="results">Результаты рассчёта.</param>
        ///<param name="client">Идентификатор клиента, выполнившего рассчёт.</param>
        [WebMethod]
        public void SetMultiPortionResult(TaskPortionResult[] results, Guid client)
        {
            foreach (var result in results)
            {
                SetPortionResult(result, client);
            }
        }
    }
}